/*
 * Copyright (C) 2010 Daniel Sundberg
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *      http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */

package nu.danielsundberg.droid.spinbox.objects;

import java.io.IOException;
import java.io.InputStream;
import java.nio.ByteBuffer;
import java.nio.ByteOrder;
import java.nio.FloatBuffer;

import javax.microedition.khronos.opengles.GL10;
import javax.microedition.khronos.opengles.GL11;

import nu.danielsundberg.droid.spinbox.R;
import nu.danielsundberg.droid.spinbox.math.Vertice3f;
import android.content.Context;
import android.graphics.Bitmap;
import android.graphics.BitmapFactory;
import android.opengl.GLUtils;
import android.util.Log;

/**
 * This class is an object representation of a Cube
 * @author Daniel Sundberg
 */
public class Cube {

	/** The buffer holding the vertices */
	private FloatBuffer vertexBuffer;
	/** The buffer holding the texture coordinates */
	private FloatBuffer textureBuffer;
	/** The buffer holding the indices */
	private ByteBuffer indexBuffer;
	/** The buffer holding the normals */
	private FloatBuffer normalBuffer;

	/** Our texture pointer */
	private int[] textures = new int[3];



	Vertice3f v0 = new Vertice3f(-1.0f, -1.0f, 1.0f); //v0 Front	
	Vertice3f v1 = new Vertice3f(1.0f, -1.0f, 1.0f); 	//v1
	Vertice3f v2 = new Vertice3f(-1.0f, 1.0f, 1.0f);	//v2
	Vertice3f v3 = new Vertice3f(1.0f, 1.0f, 1.0f);	//v3

	Vertice3f v4 = new Vertice3f(1.0f, -1.0f,  1.0f); 	// Right
	Vertice3f v5 = new Vertice3f(1.0f, -1.0f, -1.0f); 
	Vertice3f v6 = new Vertice3f(1.0f,  1.0f,  1.0f);
	Vertice3f v7 = new Vertice3f(1.0f,  1.0f, -1.0f);

	Vertice3f v8 = new Vertice3f(  1.0f, -1.0f, -1.0f); //Back
	Vertice3f v9 = new Vertice3f( -1.0f, -1.0f, -1.0f);
	Vertice3f v10 = new Vertice3f( 1.0f,  1.0f, -1.0f);
	Vertice3f v11 = new Vertice3f(-1.0f,  1.0f, -1.0f);

	Vertice3f v12 = new Vertice3f(-1.0f, -1.0f, -1.0f); //Left
	Vertice3f v13 = new Vertice3f(-1.0f, -1.0f,  1.0f);
	Vertice3f v14 = new Vertice3f(-1.0f,  1.0f, -1.0f);
	Vertice3f v15 = new Vertice3f(-1.0f,  1.0f,  1.0f);

	Vertice3f v16 = new Vertice3f(-1.0f, -1.0f, -1.0f); //Bottom 
	Vertice3f v17 = new Vertice3f( 1.0f, -1.0f, -1.0f);
	Vertice3f v18 = new Vertice3f(-1.0f, -1.0f,  1.0f); 
	Vertice3f v19 = new Vertice3f( 1.0f, -1.0f,  1.0f);

	Vertice3f v20 = new Vertice3f(-1.0f, 1.0f,  1.0f); //TOp
	Vertice3f v21 = new Vertice3f( 1.0f, 1.0f,  1.0f);
	Vertice3f v22 = new Vertice3f(-1.0f, 1.0f, -1.0f); 
	Vertice3f v23 = new Vertice3f( 1.0f, 1.0f, -1.0f);

	/** The initial vertex definition */	
	private float vertices[] = {
			//Vertices according to faces
			v0.x, v0.y, v0.z,
			v1.x, v1.y, v1.z,
			v2.x, v2.y, v2.z,
			v3.x, v3.y, v3.z,
			v4.x, v4.y, v4.z,
			v5.x, v5.y, v5.z,
			v6.x, v6.y, v6.z,
			v7.x, v7.y, v7.z,
			v8.x, v8.y, v8.z,
			v9.x, v9.y, v9.z,
			v10.x, v10.y, v10.z,
			v11.x, v11.y, v11.z,
			v12.x, v12.y, v12.z,
			v13.x, v13.y, v13.z,
			v14.x, v14.y, v14.z,
			v15.x, v15.y, v15.z,
			v16.x, v16.y, v16.z,
			v17.x, v17.y, v17.z,
			v18.x, v18.y, v18.z,
			v19.x, v19.y, v19.z,
			v20.x, v20.y, v20.z,
			v21.x, v21.y, v21.z,
			v22.x, v22.y, v22.z,
			v23.x, v23.y, v23.z

	};

	/** 
	 * The initial normals for the lighting calculations 
	 * 
	 * The normals are not necessarily correct from a 
	 * real world perspective, as I am too lazy to write
	 * these all on my own. But you get the idea and see
	 * what I mean if you run the demo.
	 * 0, 1, 3, 0, 3, 2, 		// Face front
			4, 5, 7, 4, 7, 6, 		// Face right
			8, 9, 11, 8, 11, 10, 	// ...
			12, 13, 15, 12, 15, 14, 
			16, 17, 19, 16, 19, 18, 
			20, 21, 23, 20, 23, 22,
	 */
	//	private float normals[] = {
	//				// Normals
	//			calcNormal(v0, v1, v3).x, calcNormal(v0, v1, v3).y, calcNormal(v0, v1, v3).z,			
	//			calcNormal(v0, v3, v2).x, calcNormal(v0, v3, v2).y, calcNormal(v0, v3, v2).z,
	//			calcNormal(v4, v5, v7).x, calcNormal(v4, v5, v7).y, calcNormal(v4, v5, v7).z,
	//			
	//			calcNormal(v4, v7, v6).x, calcNormal(v4, v7, v6).y, calcNormal(v4, v7, v6).z,
	//			calcNormal(v8, v9, v11).x, calcNormal(v8, v9, v11).y, calcNormal(v8, v9, v11).z,
	//			
	//			calcNormal(v8, v11, v10).x, calcNormal(v8, v11, v10).y, calcNormal(v8, v11, v10).z,
	//			calcNormal(v12, v13, v15).x, calcNormal(v12, v13, v15).y, calcNormal(v12, v13, v15).z,
	//			
	//			calcNormal(v12, v15, v14).x, calcNormal(v12, v15, v14).y, calcNormal(v12, v15, v14).z,
	//			calcNormal(v16, v17, v19).x, calcNormal(v16, v17, v19).y, calcNormal(v16, v17, v19).z,
	//			calcNormal(v16, v19, v18).x, calcNormal(v16, v19, v18).y, calcNormal(v16, v19, v18).z,
	//			calcNormal(v20, v21, v23).x, calcNormal(v20, v21, v23).y, calcNormal(v20, v21, v23).z,
	//			calcNormal(v20, v23, v22).x, calcNormal(v20, v23, v22).y, calcNormal(v20, v23, v22).z
	//	};

	private float normals[] = {
			// Normals
			0.0f, 0.0f, 1.0f, 						
			0.0f, 0.0f, 1.0f, 
			0.0f, 0.0f, 1.0f, 
			0.0f, 0.0f, 1.0f, 

			1.0f, 0.0f, 0.0f, 
			1.0f, 0.0f, 0.0f, 
			1.0f, 0.0f, 0.0f, 
			1.0f, 0.0f, 0.0f,

			0.0f, 0.0f, -1.0f, 
			0.0f, 0.0f, -1.0f, 
			0.0f, 0.0f, -1.0f, 
			0.0f, 0.0f, -1.0f,

			-1.0f, 0.0f, 0.0f, 
			-1.0f, 0.0f, 0.0f, 
			-1.0f, 0.0f, 0.0f, 
			-1.0f, 0.0f, 0.0f,

			0.0f, -1.0f, 0.0f, 
			0.0f, -1.0f, 0.0f, 
			0.0f, -1.0f, 0.0f, 
			0.0f, -1.0f, 0.0f,

			0.0f, 1.0f, 0.0f, 
			0.0f, 1.0f, 0.0f, 
			0.0f, 1.0f, 0.0f, 
			0.0f, 1.0f, 0.0f,
	};


	/** The initial texture coordinates (u, v) */	
	private float texture[] = {
			//Mapping coordinates for the vertices
			1.0f, 1.0f, 
			1.0f, 0.75f, 
			0.75f, 1.0f, 
			0.75f, 0.75f,

			1.0f, 1.0f, 
			1.0f, 0.5f, 
			0.5f, 1.0f, 
			0.5f, 0.5f,

			1.0f, 0.5f, 
			1.0f, 1.0f,  
			0.5f, 0.5f,
			0.5f, 1.0f,

			1.0f, 1.0f, 
			1.0f, 0.75f, 
			0.75f, 1.0f, 
			0.75f, 0.75f,

			0.0f, 0.0f, 
			0.0f, 1.0f, 
			1.0f, 0.0f, 
			1.0f, 1.0f,

			0.0f, 0.0f, 
			0.0f, 1.0f, 
			1.0f, 0.0f, 
			1.0f, 1.0f, 
	};

	/** The initial indices definition */
	private byte indices[] = {
			// Faces definition
			0, 1, 3, 0, 3, 2, 		// Face front
			4, 5, 7, 4, 7, 6, 		// Face right
			8, 9, 11, 8, 11, 10, 	// ...
			12, 13, 15, 12, 15, 14, 
			16, 17, 19, 16, 19, 18, 
			20, 21, 23, 20, 23, 22, 
	};


	/**
	 * The Cube constructor.
	 * 
	 * Initiate the buffers.
	 */
	public Cube() {
		//
		ByteBuffer byteBuf = ByteBuffer.allocateDirect(vertices.length * 4);
		byteBuf.order(ByteOrder.nativeOrder());
		vertexBuffer = byteBuf.asFloatBuffer();
		vertexBuffer.put(vertices);
		vertexBuffer.position(0);

		//
		byteBuf = ByteBuffer.allocateDirect(texture.length * 4);
		byteBuf.order(ByteOrder.nativeOrder());
		textureBuffer = byteBuf.asFloatBuffer();
		textureBuffer.put(texture);
		textureBuffer.position(0);

		//
		byteBuf = ByteBuffer.allocateDirect(normals.length * 4);
		byteBuf.order(ByteOrder.nativeOrder());
		normalBuffer = byteBuf.asFloatBuffer();
		normalBuffer.put(normals);
		normalBuffer.position(0);

		//
		indexBuffer = ByteBuffer.allocateDirect(indices.length);
		indexBuffer.put(indices);
		indexBuffer.position(0);
	}




	/**
	 * The object own drawing function.
	 * Called from the renderer to redraw this instance
	 * with possible changes in values.
	 * 
	 * @param gl - The GL Context
	 * @param filter - Which texture filter to be used
	 */
	public void draw(GL10 gl, int filter) {
		//Bind the texture according to the set texture filter
		gl.glBindTexture(GL10.GL_TEXTURE_2D, textures[filter]);

		//Enable the vertex, texture and normal state
		gl.glEnableClientState(GL10.GL_VERTEX_ARRAY);
		gl.glEnableClientState(GL10.GL_TEXTURE_COORD_ARRAY);
		gl.glEnableClientState(GL10.GL_NORMAL_ARRAY);

		//Set the face rotation
		gl.glFrontFace(GL10.GL_CCW);

		//Point to our buffers
		gl.glVertexPointer(3, GL10.GL_FLOAT, 0, vertexBuffer);
		gl.glTexCoordPointer(2, GL10.GL_FLOAT, 0, textureBuffer);
		gl.glNormalPointer(GL10.GL_FLOAT, 0, normalBuffer);

		//Draw the vertices as triangles, based on the Index Buffer information
		gl.glDrawElements(GL10.GL_TRIANGLES, indices.length, GL10.GL_UNSIGNED_BYTE, indexBuffer);

		//Disable the client state before leaving
		gl.glDisableClientState(GL10.GL_VERTEX_ARRAY);
		gl.glDisableClientState(GL10.GL_TEXTURE_COORD_ARRAY);
		gl.glDisableClientState(GL10.GL_NORMAL_ARRAY);
	}

	/**
	 * Load the textures
	 * 
	 * @param gl - The GL Context
	 * @param context - The Activity context
	 */
	public void loadGLTexture(GL10 gl, Context context) {
		//Get the texture from the Android resource directory
		Bitmap bitmap = null;
		try {
			//BitmapFactory is an Android graphics utility for images			
			bitmap = BitmapFactory.decodeResource(context.getResources(), R.drawable.box);
		} catch (Exception e) {
			Log.e("Spinbox", "Failed to load box texture.");
		} 
		
		//Generate there texture pointer
		gl.glGenTextures(3, textures, 0);

		//Create Nearest Filtered Texture and bind it to texture 0
		gl.glBindTexture(GL10.GL_TEXTURE_2D, textures[0]);
		gl.glTexParameterf(GL10.GL_TEXTURE_2D, GL10.GL_TEXTURE_MAG_FILTER, GL10.GL_NEAREST);
		gl.glTexParameterf(GL10.GL_TEXTURE_2D, GL10.GL_TEXTURE_MIN_FILTER, GL10.GL_NEAREST);
		GLUtils.texImage2D(GL10.GL_TEXTURE_2D, 0, bitmap, 0);

		//Create Linear Filtered Texture and bind it to texture 1
		gl.glBindTexture(GL10.GL_TEXTURE_2D, textures[1]);
		gl.glTexParameterf(GL10.GL_TEXTURE_2D, GL10.GL_TEXTURE_MAG_FILTER, GL10.GL_LINEAR);
		gl.glTexParameterf(GL10.GL_TEXTURE_2D, GL10.GL_TEXTURE_MIN_FILTER, GL10.GL_LINEAR);
		GLUtils.texImage2D(GL10.GL_TEXTURE_2D, 0, bitmap, 0);

		//Create mipmapped textures and bind it to texture 2
		gl.glBindTexture(GL10.GL_TEXTURE_2D, textures[2]);
		gl.glTexParameterf(GL10.GL_TEXTURE_2D, GL10.GL_TEXTURE_MAG_FILTER, GL10.GL_LINEAR);
		gl.glTexParameterf(GL10.GL_TEXTURE_2D, GL10.GL_TEXTURE_MIN_FILTER, GL10.GL_LINEAR_MIPMAP_NEAREST);
		/*
		 * This is a change to the original tutorial, as buildMipMap does not exist anymore
		 * in the Android SDK.
		 * 
		 * We check if the GL context is version 1.1 and generate MipMaps by flag.
		 * Otherwise we call our own buildMipMap implementation
		 */
		if(gl instanceof GL11) {
			gl.glTexParameterf(GL11.GL_TEXTURE_2D, GL11.GL_GENERATE_MIPMAP, GL11.GL_TRUE);
			GLUtils.texImage2D(GL10.GL_TEXTURE_2D, 0, bitmap, 0);
		} else {
			buildMipmap(gl, bitmap);
		}		

		//Clean up
		bitmap.recycle();
	}

	private void buildMipmap(GL10 gl, Bitmap bitmap) {

		int level = 0;

		int height = bitmap.getHeight();
		int width = bitmap.getWidth();

		while(height >= 2 || width >= 2) {
			//First of all, generate the texture from our bitmap and set it to the according level
			GLUtils.texImage2D(GL10.GL_TEXTURE_2D, level, bitmap, 0);

			//
			if(height == 2 || width == 2) {
				break;
			}

			//Increase the mipmap level
			level++;

			//
			height /= 2;
			width /= 2;
			Bitmap bitmap2 = Bitmap.createScaledBitmap(bitmap, width, height, true);

			//Clean up
			bitmap.recycle();
			bitmap = bitmap2;
		}
	}
}